插件设计

注:本功能需要有代码能力,若不会代码建议使用 0 代码方案解决(使用 市场插件提交需求)

1. 简介

简道云开放平台以插件为载体,允许用户在多个功能场景中,通过可插拔的自定义代码逻辑,实现一些定制需求。插件可以被分发,插件的用户无需关注代码等技术细节。

创建插件后,系统会自动为您在插件内创建一个函数。一个插件可以包含一个或多个函数。每个函数主要包含3个属性: 代码、请求参数声明(以下简称入参声明)和返回参数声明(以下简称出参声明)。

通过请求参数声明定义插件使用者需要向您的函数传递哪些参数,通过返回参数声明定义您将为用户输出哪些结果,并最终编写代码来实现从参数到结果的转换或其他自定义逻辑。

以一个翻译插件为例,您可以定义用户输入一个文本,您通过编写代码,以调用第三方 API 的方式将用户的文本翻译为日语并将结果返回给用户。用户可以将该结果回填到表单的其他字段。

1)插件设计页面及各部分功能见下表:

序号

对应功能

功能说明

插件名称

——

通用参数

在这里添加每个函数都需要依赖的参数,如指定平台的秘钥

后端函数/前端扩展

由入参声明、出参声明和代码组成。

  • 后端函数:后端完成插件执行过程。
  • 前端扩展:在前端执行插件,可调用一些前端行为,比如:弹出iframe弹窗。

可选字段

您通过拖拽字段设计参数,不同类型的字段将在用户配置/使用时产生不同的效果。

请求参数/返回参数声明

您设计的请求参数,用户在使用插件时,需要为这里的每个参数指定值,这些值可以由用户直接填写,也可以配置为引用其他功能中的数据,简道云将为您完成数据绑定,您无需关注参数的来源。

您设计的返回参数,用户在使用插件时,可以配置是否使用、存储至指定字段。

单个参数的详细配置

  • 定义ID 便于在代码中对变量进行引用。
  • 使用相关提示和默认值便于您的用户理解该配置的含义。

2)设计页面中的相关配置项,在前台使用插件时对应如下:

2. 新增函数

插件新建完成后,需要新增对应的函数。插件中的函数分为以下 2 种类型,在「插件设计」页面中,点击左下角的「新增函数」,您可根据自己的需求选择不同的函数进行创建:

函数类型

函数释义

可选位置

备注

前端扩展

若设置动作为前端扩展,则在前端执行插件,可调用一些前端行为。

比如:弹出 iframe 弹窗。

场景举例:弹出地图页面;录音。

只有 前端事件 处可以选到该动作。

目前仅支持作为按钮字段的执行动作。

不计费不参与后台触发次数统计。

后端函数

若设置动作为后端函数,则后端完成插件执行过程。

场景举例:企业微信群机器人;excel 解析。

所有调用插件的位置均可选到该动作。

/

注:

1)前端扩展与后端函数均包含:请求参数、返回参数、代码部分。请求参数和返回参数规则一致,代码部分各自支持的方法见下文【4. 代码编辑】。

2)单个自建插件内的函数数量上限为 30 个。

3. 参数设计

插件有 4 种类型的参数可以配置,四者都非必须,按场景所需添加即可:

参数类型

含义

数量限制

1

身份验证

身份验证是指用户在配置插件时需要填写应用的相关信息,从而对插件使用者身份进行验证,保证插件调用时的安全。

——

2

通用参数

通用参数是使用插件时要配置的参数针对整个插件的参数。通常是在启用插件前,对插件进行的整体配置,比如调用简道云接口的 API Key。

1)通用参数/单个请求参数内/单个返回参数内字段数量上限为 128 个。

2)请求参数中,提示内容字段长度上限为 4096。

3

请求参数

请求参数是在不同位置(比如前端事件/智能助手)中使用插件时,插件使用者需要提供的内容。比如:调用简道云部门接口时,需要使用者提供部门信息。

4

返回参数

返回参数是指插件执行完成后,返回给插件使用者的内容。插件使用者可以选择需要的内容回填至表单中。

3.1 身份验证

1)身份验证是指用户在配置插件时需要填写应用的相关信息。插件设计过程中,您可选择「无」不验证身份,也可根据不同的集成模式进行身份验证,简道云中支持通过以下 6 种方式验证身份:

  • 钉钉自建插件
  • 企业微信应用
  • 飞书自建应用
  • 微信公众平台
  • Basic Auth
  • OAuth2.0 - Client Credentials

2)当选择身份验证方式为钉钉自建应用、企业微信应用、飞书自建应用和微信公众平台时,插件配置时需输入对应的应用 ID、应用 Key 和应用 Secret 进行身份验证。以选择钉钉自建应用为例,插件配置时效果如下所示:

注:仅当选择企业微信应用时,需配置应用 ID、应用 Key 和应用 Secret 进行验证。

3)当选择身份验证方式为 Basic Auth 时,插件配置时需输入对应的 Username 和 Password 进行身份验证。用户也可点击「添加参数」来设置更多身份验证选项,同时可在右侧属性栏中设置参数相关属性。

以授权参数仅为 Username 和 Password 为例,插件配置效果如下所示:

注:授权参数上限为 5 个。

4)当选择身份验证方式为 OAuth2.0 - Client Credentials 时,验证方式支持设置以下内容:

  • 授权参数:默认为 ClientID 和 ClientSecret 授权参数进行验证,可点击「添加参数」来设置更多身份验证选项,同时可在右侧属性栏中设置参数相关属性。
  • 接口参数:支持设置请求类型、URL 和 Header/Body;
  • 返回参数:支持设置 accessToken 和 Token 有效期(秒)。

以授权参数仅为 ClientID 和 ClientSecret 为例,插件配置效果如下所示:

注:授权参数上限为 5 个。

3.2 通用参数

通用参数是使用插件时要配置的参数针对整个插件的参数。通常是在启用插件前,对插件进行的配置。

如,对接第三方平台需要配置的 API Key 等参数均可以在通用参数中设置。

注:

1)根据参数需要填写的内容选择字段,通用参数仅支持基础字段的设置:文本、数字、日期时间、下拉框。

2)通用参数内的字段数量上限为 128 个。

3.3 请求参数

请求参数是在不同位置(比如前端事件/智能助手)中使用插件时,插件使用者需要提供的内容。比如:调用简道云部门接口时,需要使用者提供部门信息。

在设计请求参数时,可以选择不同类型的字段。不同的字段类型影响插件用户在配置插件时的输入方式,以及您在代码中获取到的值的类型。

举例,您在入参声明中添加了一个「部门选择」类型的参数,那么您的用户在配置插件时,将只能在界面上选择部门类型的值,或引用表单数据中支持转换为该类型的字段,比如「部门单选」/「部门多选」。

注:

1)请求参数不同类型对于表单字段存储格式的支持,可以查看 【6. 相关信息】 了解。

2)单个请求参数内字段上限数量为 128 个。

3)请求参数中,提示内容字段长度上限为 4096。

3.4 返回参数

返回参数是指插件执行完成后,返回给插件使用者的内容。插件使用者可以选择需要的内容回填至表单中,或传递给其他插件的其他函数。比如:调用部门接口后,将会返回部门下成员信息,可以将成员信息定义成返回参数。

注:单个返回参数内字段上限数量为 128 个。

2)参数类型可选择 any、object[]。

默认为 any,适用于请求参数与返回参数一对一的情况。

object[],适用于请求参数与返回参数一对多的情况,返回信息为数组,比如通过部门查询部门下成员时,可能有多个成员。

若设置了参数类型为 object[],则可通过点击列表头前的「+ 号」按钮,向下新增一行,作为子节点,如下所示。子节点的含义为数组内的具体元素,比如:返回多个成员,每个成员信息包含 成员昵称、成员编号等。

3)返回参数在前端事件插件中,配置效果如下所示:

3.5 表单校验提示信息

表单视图下配置通用参数、请求参数和返回参数时,支持对输入内容进行格式提示和实时校验,以降低输入错误的可能性,便于开发者校验修改表单配置,提高开发效率。效果如下图所示:

4. 代码编辑

参数设计确定后,您就需要为函数编写代码来实现其功能。

您的代码将被包裹于对应编程语言的一个函数之中,在代码中可以根据需要,通过 triggerConf 这一全局变量来获取定义的请求参数值,通过 return 关键字来返回最终的结果。

在编程语言选择处(上图 1)选择您熟悉的编程语言,在代码编辑器(上图 2)中编写函数代码。在您的代码中,可以引用您在入参声明中定义的参数,参数列表和值的类型可以参考右侧的(上图 3)中的提示。

4.1 后端函数-示例代码及说明

Python 3.6

# 可以引用一些第三方库.
import json
import requests

# 可以通过读取预定义的全局变量中的属性来获取您定义的参数.
# agentConf 含义是通用参数, 其结构为一个字典(dict), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值.
# triggerConf 含义是请求参数, 其结构为一个字典(dict), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值或表单字段的值.
api_key = str(agentConf.get('apiKey'))
dept_no = triggerConf.get('deptNo')

# 您需要对输入参数的类型, 格式做校验, 以增强函数逻辑的健壮性.
# 如下方对可能拿到的不同值格式进行处理,保证最终dept_no的准确性,具体值格式见本文「5.1 triggerConf (请求参数)结构」
try:
    if isinstance(dept_no, str):
        dept_no = json.loads(dept_no)
    if isinstance(dept_no, list):
        dept_no = dept_no[0] if 0 < len(dept_no) else None
    if isinstance(dept_no, dict):
        dept_no = dept_no.get('dept_no')
    if dept_no is None:
        dept_no = 1
    dept_no = int(dept_no)
except:
    raise ValueError('部门编号格式错误')

# 可以利用第三方库和请求参数, 在服务端环境中调用相关接口并获取结果.
url = ' https://api.jiandaoyun.com/api/v5/corp/department/user/list'
payload = {'dept_no': dept_no}
headers = {'Authorization': 'Bearer ' + api_key}
response = requests.post(url, json=payload, headers=headers)
if 300 <= response.status_code < 500:
    # 对特定结果主动抛错, 定义抛错文案.
    body = response.json()
    code = body.get('code', -1)
    message = body.get('message', '未知错误')
    message = '参数错误(%s): %s' % (code, message)
    raise ValueError(message)
# 对结果进行处理, 定义返回参数值.
# 您需要返回一个dict, dict的key和返回参数ID一一对应. 若返回为数组, 则对应出参需设置为object[]类型.
body = response.json()
users = [{'name': user.get('name'), 'username': user.get(
    'username')} for user in body.get('users', [])]
count = len(users)
return {
    "users": users,
    "count": count
}

# 可引用的全局变量, 支持的第三方库, 请求参数数据存储格式, 见本文「5.相关信息」.

Node.js 12

// 可以引用一些第三方库.
const _ = require('lodash');
const axios = require('axios');

// 可以通过读取预定义的全局变量中的属性来获取您定义的参数.
// agentConf 含义是通用参数, 其结构为一个对象(Object), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值.
// triggerConf 含义是请求参数, 其结构为一个对象(Object), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值或表单字段的值.
const apiKey = _.chain(agentConf).get(['apiKey']).trim().value();
let deptNo = _.get(triggerConf, ['deptNo']);

// 您需要对输入参数的类型, 格式做校验, 以增强函数逻辑的健壮性.
// 如下方对可能拿到的不同值格式进行处理,保证最终dept_no的准确性,具体值格式见本文「5.1 triggerConf (请求参数)结构」
try {
    if (_.isString(deptNo)) deptNo = JSON.parse(deptNo);
    if (_.isArray(deptNo)) deptNo = _.first(deptNo);
    if (_.has(deptNo, ['dept_no'])) deptNo = _.get(deptNo, ['dept_no']);
    deptNo = _.toInteger(deptNo);
} catch (e) {
    throw new Error('请输入正确的部门编号');
}

// 可以利用第三方库和请求参数, 在服务端环境中调用相关接口并获取结果.
try {
    const response = await axios({
        method: 'post',
        url: ' https://api.jiandaoyun.com/api/v5/corp/department/user/list',
        headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
        },
        data: { dept_no: deptNo }
    });
    const { data } = response;
    // 对结果进行处理, 定义返回参数值.
  	// 您需要返回一个object, object的key和返回参数ID一一对应. 若返回为数组, 则对应出参需设置为object[]类型.
    const users = _.chain(data)
    	.get(['users'])
    	.map((it) => ({ name: it.name, username: it.username }))
    	.value();
    return { users, count: users.length };
} catch (e) {
    // 对特定结果主动抛错, 定义抛错文案.
    const code =
        _.get(e, ['response', 'data', 'code']) || _.get(e, ['code']) || -1;
    let message =
        _.get(e, ['response', 'data', 'msg']) ||
        _.get(e, ['message']) ||
        '未知错误';
    message = `参数错误(${code}): ${message}`;
    throw new Error(message);
}

// 可引用的全局变量, 支持的第三方库, 请求参数数据存储格式, 见本文「6.相关信息」

4.2 前端扩展-示例代码及说明

// closeModal
const close = () => {
    $g.utils.closeModal();
    console.log('4s后模态框关闭');
};

const reportUrl = $g.env.baseUrl;

setTimeout(() => close(), 4000);

// GET
let response = await fetch(reportUrl, {method: 'GET'});
const resText = await response.text()
console.log(resText);

let message;

$g.ui.onmessage = function (msg) {
    message = msg
}

// openModal
// openModal:弹窗后继续代码执行,执行完结束 Worker。
// await openModal:弹窗后阻塞代码执行,按照开发者代码规定执行后续动作。
await $g.utils.openModal({
    title: '如果您对插件有任何需求,欢迎在本表单填写',
    url: reportUrl
});

// 调用后端函数
await $g.utils.callFunction({
    name: 'func.ID',
    data: {}
})

return {
    resText,
    message
}

4.3 参数提示的使用

代码编辑器右侧可展开参数提示,点击对应参数/函数,将添加对应调用语法到代码编辑器中。

4.4 参数校验提示信息

编写请求参数/返回参数/通用参数时,若出现代码错误,对应代码将用红色波浪线标注,且当鼠标放在对应代码上时,会显示详细的配置错误信息,便于开发者校验修改代码内容,提升编辑体验。效果如下所示:

4.5 语法错误提示信息

在前端扩展/后端函数的 Python 代码视图下,编辑器中支持在保存代码时提示语法错误。帮助开发者快速定位具体问题,为开发者提供更为直观、高效的开发体验。效果如下图所示:

5. 效果展示

1)后端函数插件配置及执行效果如下所示:

2)前端扩展插件配置及执行效果如下所示:

6. 相关信息

6.1 全局变量

您在代码中可以直接引用全局变量 triggerConf、agentConf、triggerContext。

agentConf (通用参数)结构

agentConf 即插件对应的通用参数。

其结构为对应编程语言的键值数据结构,如 Python 的 dict 或 Node.js 的 Object。在该结构中,键为您在入参声明中为变量指定的 ID,值为用户配置或引用的数据。

triggerConf (请求参数)结构

triggerConf 即插件函数对应的请求参数。

其结构为对应编程语言的键值数据结构,如 Python 的 dict 或 Node.js 的 Object。在该结构中,键为您在入参声明中为变量指定的 ID,值为用户配置或引用的数据(具体结构见下文)。

简道云会根据您在入参声明中选择的控件类型和用户配置该参数值的来源,在运行时尝试为您做必要的类型转换,以使您在插件代码中能够取到格式合理的值。但这个过程不是严格保证的,在复杂场景中,您可能会读取到各种预期内或预期外类型的参数,您应当在编写代码时考虑这一情况,并在不能处理相关类型时抛出合理的错误以指引用户调整其配置。

举例,您正在开发一个图像识别类的插件,并在该插件的请求参数中加入了一个文本字段用以接受用户的图片附件,那么基于不同的用户配置,您有可能得到不同的值:

  • 用户可能直接填写了一个文件的 URL,此时您将得到一个内容为"https://。。。"的字符串。
  • 用户可能关联到简道云表单数据中的图片/附件控件值上,此时您将得到一个内容为'[{"url":"https://。。。","name":"。。。"},{"url":"https://。。。","name":"。。。"}]'的 JSON 字符串。
  • 用户可能关联到简道云表单数据中的手写签名/微信头像控件值上,此时您将得到一个'{"url":"https://。。。","name":"。。。"}'的 JSON 字符串。
  • 用户可能关联到另一个插件的返回值的一个属性上,此时您可能得到一个类型不确定的值。

您应该结合自己插件的功能和预期服务的用户场景,在代码中妥善处理上述情况,并在不计划支持该类型时通过抛出错误并附加消息向用户提示原因。

请求参数储值格式

1)保存自定义值

当用户在插件配置时直接保存「自定义值」时(如上图),请求参数中不同控件的储值格式如下表:

控件

储值结构

文本

"你好, 世界"

下拉框

"选项1"

数字

3.14

日期时间

"2022-09-09T08:00:00.000Z"

成员选择

[{

"_id": "5b433bf80118dc44bcb9183b",

"name": "lizijun",

"username": "lizijun",

"status": 1,

"type": 0

}]

部门选择

[{

"_id": "602f77c86aee9d2dd4c04bfc",

"name": "一级部门",

"dept_no": 3,

"type": 0

}]

2)保存字段

当用户在简道云表单数据场景中使用插件时,可保存字段值(如上图),入参声明中各个控件的储值格式如下表:

表单控件 \ 入参声明控件

字段值

模板字符串

文本

下拉框

数字

日期时间

成员选择

部门选择

单行文本

"你好, 世界"

值:

"你好, 世界"

ID:

"_widget_1653375890324"

多行文本

"你好, 世界\n我能吞下玻璃而不伤身体"

值:

"你好, 世界\n我能吞下玻璃而不伤身体"

ID:

"_widget_1653375890324"

数字

3.14

3.14

值:

3.14

ID:

"_widget_1653375890324"

日期时间

"2022-09-09T08:00:00.000Z"

"2022-09-09T08:00:00.000Z"

值:

"2022-09-09 16:00:00"

ID:

"_widget_1653375890324"

单选按钮组

"选项1"

值:

"选项1"

ID:

"_widget_1653375890324"

复选框组

'["选项1","选项3"]'

值:

"选项1, 选项3"

ID:

"_widget_1653375890324"

下拉框

"选项1"

值:

"选项1"

ID:

"_widget_1653375890324"

下拉复选框

'["选项1","选项3"]'

值:

"选项1, 选项3"

ID:

"_widget_1653375890324"

地址

'{"province":"山西省","city":"太原市","district":"尖草坪区","detail":"迎新街俱乐部"}'

值:

"山西省太原市尖草坪区迎新街俱乐部"

ID:

"_widget_1653375890324"

定位

'{"province":"山西省","city":"太原市","district":"尖草坪区","detail":"迎新街俱乐部", "lnglatXY":[112.549656,37.870451}'

值:

"山西省太原市尖草坪区迎新街俱乐部"

ID:

"_widget_1653375890324"

图片

'[{"name":"emoji.gif","size":10168,"mime":"image/gif","url":"https://..."}]'

值:

ID:

"_widget_1653375890324"

附件

'[{"name":"emoji.jpg","size":10168,"mime":"image/jpeg","url":"https://..."}]'

值:

ID:

"_widget_1653375890324"

手写签名

{"name":"signature_1663590240652.png","size":8800,"mime":"image/png","url":"https://..."}

值:

ID:

"_widget_1653375890324"

流水号

"00005"

值:

"00005"

ID:

"_widget_1653375890324"

手机

'{"verified":false,"phone":"155..."}'

值:

"15536090976"

ID:

"_widget_1653375890324"

成员单选

'[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]'

'[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]'

值:(name)

"lizijun"

ID:

"_widget_1653375890324"

成员多选

'[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]'

'[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]'

值:(name)

"lizijun, springmoon"

ID:

"_widget_1653375890324"

部门单选

'[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]'

'[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]'

值:(name)

"帆软软件有限公司"

ID:

"_widget_1653375890324"

部门多选

'[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]'

'[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]'

值:(name)

"帆软软件有限公司, springmoon的企业/团队"

ID:

"_widget_1653375890324"

triggerContext 结构

triggerContext 中存储了运行环境的基本信息,如简道云的开放 API 域名等。

6.2 内置库支持列表

Node.js

  • axios(0.21.0);
  • form-data(2.3.3);
  • lodash(4.17.20);
  • moment-timezone(0.5.32)、mongodb(3.6.3);
  • tedious(9.2.1)
  • sequelize(6.3.5)

注:sequelize 目前支持的数据库(mysql,mssql,postgres)。

Python

以下列表里包括可调用的内置库和第三方库。

  • abc、 argparse、 array、asynchat、asyncio、asyncore、audioop;
  • base64、binascii、binhex、bisect、bz2;
  • calendar、cgi、cgitb、chunk、cmath、cmd、codecs、collections、colorsys、configparser、contextlib、contextvars、copy、copyreg、crypt、csv、ctypes
  • dataclasses、datetime、dbm、decimal、difflib、doctest;
  • email、encodings、enum、errno;
  • fnmatch、formatter、fractions、ftplib、functools;
  • getopt、gettext、glob、graphlib、grp、gzip;
  • hashlib、heapq、hmac、html、http;
  • imaplib、 imghdr、 ipaddress、 itertools;
  • json;
  • keyword;
  • linecache、 locale、 logging、 lzma;
  • mailbox、 mailcap、 math、 mimetypes、 mmap;
  • netrc、 nis、 nntplib、 numbers;
  • operator、 optparse;
  • parser、 poplib、 pprint;
  • quopri;
  • random、 re、 reprlib;
  • secrets、 select、 selectors、 shlex、 smtpd、 smtplib、 sndhdr、 socket、 socketserver、sqlite3、 ssl、 statistics、 string、 stringprep、 struct、 sunau;
  • tarfile、 telnetlib、 test、 textwrap、 time、 timeit、 types、 typing;
  • unicodedata、 unittest、 urllib、 uu、 uuid;
  • warnings、 wave、 weakref、 wsgiref;
  • xml、 xmlrpc;
  • zlib、 zoneinfo;
  • requests(2.26.0)、 pymysql(1.0.2)、pymssql(2.2.5)、pymongo(3.12.1)、psycopg2_binary(2.9.3)
  • paramiko(2.8.0)、 pyjwt(2.3.0)、lxml(4.6.3)、xmltodict(0.12.0)、jinja2(2.11.3);
  • cryptography(35.0.0)。

6.3 可用前端扩展API

可用API名称

含义

代码示例

$g.utils.openModal

打开弹窗

$g.utils.openModal({

title: '弹窗标题',

url: '弹窗打开链接'

})

$g.utils.closeModal

关闭弹窗

$g.utils.closeModal()

$g.utils.callFunction

调用后端函数

$g.utils.callFunction({

name: 'func.ID',

data: {}

})

$g.utils.openUrl

新标签页打开 URL

$g.utils.openUrl({

url: '链接地址'

})

$g.ui.onmessage

接收弹窗 iframe 内发送的消息

// 插件

$g.ui.onmessage = (message) => {

// 对应 postMessage 中 pluginMessage 的值

console.log(message)

}

//弹窗 iframe

parent.postMessage({pluginMessage: 'any'}, '*')

  • 格式固定为 {pluginMessage: 'message'}
  • message 为任意类型

注:历史 openModal、closeModal 和 callFunction 等用法仍兼容。

7. 注意事项

7.1 插件运行限制

插件在运行时有相应的限制。插件本身的超时时间为 60 s,但是不同使用场景本身也有自己的超时时间,比如前端事件的 20 s。

7.2 前端扩展调用后端函数

进行自建插件设计时,若在「前端扩展函数 >> 代码」处调用了后端函数,则插件执行时,前端扩展中的后端函数执行上限为 5 次。

以中奖通知为例,在表单内点击「新建邮件」便会打开编辑窗口,填写并点击「发送邮件」即可完成通知。其中,「新建邮件」为前端扩展,「发送邮件」为后端函数,则每次打开新建邮件弹窗后,发送邮件的上限为 5 次。

注:若对后端函数设置了「按次收费」,则在前端函数中多次调用后端函数时,将会多次扣费。

文档内容是否对您有帮助?
有帮助
没帮助没帮助
如需获取即时帮助,请联系技术支持
咨询
扫码领取100+零代码资料简道云官方微信号400-111-0890
图标在线咨询
立即体验